﻿/** \file
* \$Rev: 186 $
* 
* \$Date: 2010-04-16 05:58:01 +0000 (Fri, 16 Apr 2010) $
*
* \$URL: http://chorus-twitter.googlecode.com/svn/trunk/src/utils/Mergeable.cs $
*/

using System;
using System.Reflection;

namespace chorus.utils
{
	/// <summary>
	/// A class to help with merging. When instantiated, it does the merge in
	/// the constructor.
	/// </summary>
	/// <typeparam name="MemberType">The MemberInfo type.</typeparam>
	public abstract class Merger<MemberType>
		where MemberType : MemberInfo
	{
		/// <summary>The BindingFlags to use when searching for members.</summary>
		protected const BindingFlags flags = BindingFlags.Instance |
			BindingFlags.Public | BindingFlags.NonPublic;
		/// <summary>The destination object.</summary>
		protected Mergeable a;
		/// <summary>The source object.</summary>
		protected Mergeable b;
		/// <summary>The destination type.</summary>
		protected Type typea;
		/// <summary>The source type.</summary>
		protected Type typeb;

		/// <summary>The constructor. Merges b into a.</summary>
		/// <param name="a">The destination object.</param>
		/// <param name="b">The source object.</param>
		public Merger(Mergeable a, Mergeable b)
		{
			this.a = a;
			this.b = b;
			typea = a.GetType();
			typeb = b.GetType();
			foreach (MemberType mb in BMembers)
				Merge(mb);
		}

		/// <summary>Do a merge of a single member.</summary>
		/// <param name="mb">The MemberInfo of the source.</param>
		protected virtual void Merge(MemberType mb)
		{
			object bval = GetBValue(mb);
			if (bval != null)
			{
				AbstractSerializable abs = bval as AbstractSerializable;
				if (abs == null || abs.IsSet)
				{
					try { SetAValue(mb, bval); }
					// Swallow this exception if the destination parameter does not exist
					catch (ArgumentException) { }
				}
			}
		}

		/// <summary>The members of the source.</summary>
		protected abstract MemberType[] BMembers { get; }
		/// <summary>Get the value of a member in the source.</summary>
		/// <param name="member">The MemberInfo of the source member.</param>
		/// <returns>The value of that member.</returns>
		protected abstract object GetBValue(MemberType member);
		/// <summary>Set the value of a member in the destination.</summary>
		/// <param name="member">The MemberInfo for the detination member.</param>
		/// <param name="value">The value of the member to set.</param>
		protected abstract void SetAValue(MemberType member, object value);
	}

	/// <summary>
	/// A class to merge fields of two objects.
	/// </summary>
	public class FieldMerger : Merger<FieldInfo>
	{
		/// <summary>The constructor. Merges b into a.</summary>
		/// <param name="a">The destination object.</param>
		/// <param name="b">The source object.</param>
		public FieldMerger(Mergeable a, Mergeable b) : base(a, b) { }
		/// <summary>The members of the source.</summary>
		protected override FieldInfo[] BMembers { get { return typeb.GetFields(flags); } }
		/// <summary>Get the value of a member in the source.</summary>
		/// <param name="member">The MemberInfo of the source member.</param>
		/// <returns>The value of that member.</returns>
		protected override object GetBValue(FieldInfo member) { return member.GetValue(b); }
		/// <summary>Set the value of a member in the destination.</summary>
		/// <param name="member">The MemberInfo for the detination member.</param>
		/// <param name="value">The value of the member to set.</param>
		protected override void SetAValue(FieldInfo member, object value) { member.SetValue(a, value); }
	}

	/// <summary>
	/// A class to merge properties of two objects.
	/// </summary>
	public class PropMerger : Merger<PropertyInfo>
	{
		/// <summary>The constructor. Merges b into a.</summary>
		/// <param name="a">The destination object.</param>
		/// <param name="b">The source object.</param>
		public PropMerger(Mergeable a, Mergeable b) : base(a, b) { }
		/// <summary>The members of the source.</summary>
		protected override PropertyInfo[] BMembers { get { return typeb.GetProperties(flags); } }
		/// <summary>Get the value of a member in the source.</summary>
		/// <param name="member">The MemberInfo of the source member.</param>
		/// <returns>The value of that member.</returns>
		protected override object GetBValue(PropertyInfo member) { return member.GetValue(b, null); }
		/// <summary>Set the value of a member in the destination.</summary>
		/// <param name="member">The MemberInfo for the detination member.</param>
		/// <param name="value">The value of the member to set.</param>
		protected override void SetAValue(PropertyInfo member, object value) { member.SetValue(a, value, null); }

		/// <summary>Do a merge of a single member.</summary>
		/// <param name="mb">The MemberInfo of the source.</param>
		protected override void Merge(PropertyInfo mb)
		{
			if (mb.CanRead && mb.CanWrite)
				base.Merge(mb);
		}
	}

	/// <summary>
	/// A class whose instances can be merged member-by-member.
	/// </summary>
	public abstract class Mergeable
	{
		/// <summary>
		/// Merge an object into this object member by member. Properties and
		/// fields are copied from "from" to "this" if both objects have the
		/// member and the member in "from" is not null.
		/// </summary>
		/// <param name="from">The object to merge.</param>
		public void MergeFrom(Mergeable from)
		{
			new FieldMerger(this, from);
			new PropMerger(this, from);
		}
	}
}
